@openrig/cli 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/daemon/assets/guidance/openrig-start.md +6 -0
  2. package/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md +49 -21
  3. package/daemon/assets/plugins/openrig-core/skills/openrig-user/SKILL.md +12 -12
  4. package/daemon/dist/build-info.d.ts +12 -0
  5. package/daemon/dist/build-info.d.ts.map +1 -0
  6. package/daemon/dist/build-info.js +11 -0
  7. package/daemon/dist/build-info.js.map +1 -0
  8. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.d.ts +18 -0
  9. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.d.ts.map +1 -0
  10. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.js +22 -0
  11. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.js.map +1 -0
  12. package/daemon/dist/domain/feed/attention-aggregator.d.ts +31 -0
  13. package/daemon/dist/domain/feed/attention-aggregator.d.ts.map +1 -0
  14. package/daemon/dist/domain/feed/attention-aggregator.js +119 -0
  15. package/daemon/dist/domain/feed/attention-aggregator.js.map +1 -0
  16. package/daemon/dist/domain/files/file-write-service.d.ts +11 -2
  17. package/daemon/dist/domain/files/file-write-service.d.ts.map +1 -1
  18. package/daemon/dist/domain/files/file-write-service.js +77 -0
  19. package/daemon/dist/domain/files/file-write-service.js.map +1 -1
  20. package/daemon/dist/domain/hosts/fanout-contract.d.ts +33 -0
  21. package/daemon/dist/domain/hosts/fanout-contract.d.ts.map +1 -0
  22. package/daemon/dist/domain/hosts/fanout-contract.js +23 -0
  23. package/daemon/dist/domain/hosts/fanout-contract.js.map +1 -0
  24. package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts +49 -0
  25. package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts.map +1 -0
  26. package/daemon/dist/domain/hosts/hosts-registry-reader.js +165 -0
  27. package/daemon/dist/domain/hosts/hosts-registry-reader.js.map +1 -0
  28. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts +35 -0
  29. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts.map +1 -0
  30. package/daemon/dist/domain/hosts/remote-daemon-http.js +105 -0
  31. package/daemon/dist/domain/hosts/remote-daemon-http.js.map +1 -0
  32. package/daemon/dist/domain/human-route-enforcer.d.ts +71 -0
  33. package/daemon/dist/domain/human-route-enforcer.d.ts.map +1 -0
  34. package/daemon/dist/domain/human-route-enforcer.js +80 -0
  35. package/daemon/dist/domain/human-route-enforcer.js.map +1 -0
  36. package/daemon/dist/domain/mission-control/audit-browse.d.ts +8 -0
  37. package/daemon/dist/domain/mission-control/audit-browse.d.ts.map +1 -1
  38. package/daemon/dist/domain/mission-control/audit-browse.js +19 -0
  39. package/daemon/dist/domain/mission-control/audit-browse.js.map +1 -1
  40. package/daemon/dist/domain/mission-control/mission-control-action-log.d.ts +1 -1
  41. package/daemon/dist/domain/mission-control/mission-control-action-log.d.ts.map +1 -1
  42. package/daemon/dist/domain/mission-control/mission-control-action-log.js +4 -0
  43. package/daemon/dist/domain/mission-control/mission-control-action-log.js.map +1 -1
  44. package/daemon/dist/domain/mission-control/mission-control-fleet-cli-capability.js +1 -1
  45. package/daemon/dist/domain/mission-control/mission-control-fleet-cli-capability.js.map +1 -1
  46. package/daemon/dist/domain/mission-control/mission-control-read-layer.js +1 -1
  47. package/daemon/dist/domain/mission-control/mission-control-read-layer.js.map +1 -1
  48. package/daemon/dist/domain/mission-control/mission-control-write-contract.d.ts +28 -0
  49. package/daemon/dist/domain/mission-control/mission-control-write-contract.d.ts.map +1 -1
  50. package/daemon/dist/domain/mission-control/mission-control-write-contract.js +125 -0
  51. package/daemon/dist/domain/mission-control/mission-control-write-contract.js.map +1 -1
  52. package/daemon/dist/domain/ps-projection.d.ts +28 -1
  53. package/daemon/dist/domain/ps-projection.d.ts.map +1 -1
  54. package/daemon/dist/domain/ps-projection.js +39 -0
  55. package/daemon/dist/domain/ps-projection.js.map +1 -1
  56. package/daemon/dist/domain/queue-repository.d.ts +25 -1
  57. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  58. package/daemon/dist/domain/queue-repository.js +111 -7
  59. package/daemon/dist/domain/queue-repository.js.map +1 -1
  60. package/daemon/dist/domain/review/brief-spine.d.ts +19 -0
  61. package/daemon/dist/domain/review/brief-spine.d.ts.map +1 -0
  62. package/daemon/dist/domain/review/brief-spine.js +86 -0
  63. package/daemon/dist/domain/review/brief-spine.js.map +1 -0
  64. package/daemon/dist/domain/review/compose.d.ts +228 -0
  65. package/daemon/dist/domain/review/compose.d.ts.map +1 -0
  66. package/daemon/dist/domain/review/compose.js +773 -0
  67. package/daemon/dist/domain/review/compose.js.map +1 -0
  68. package/daemon/dist/domain/review/freeze.d.ts +31 -0
  69. package/daemon/dist/domain/review/freeze.d.ts.map +1 -0
  70. package/daemon/dist/domain/review/freeze.js +257 -0
  71. package/daemon/dist/domain/review/freeze.js.map +1 -0
  72. package/daemon/dist/domain/review/gather.d.ts +100 -0
  73. package/daemon/dist/domain/review/gather.d.ts.map +1 -0
  74. package/daemon/dist/domain/review/gather.js +676 -0
  75. package/daemon/dist/domain/review/gather.js.map +1 -0
  76. package/daemon/dist/domain/review/types.d.ts +258 -0
  77. package/daemon/dist/domain/review/types.d.ts.map +1 -0
  78. package/daemon/dist/domain/review/types.js +35 -0
  79. package/daemon/dist/domain/review/types.js.map +1 -0
  80. package/daemon/dist/domain/scope/scope-approve.d.ts +64 -0
  81. package/daemon/dist/domain/scope/scope-approve.d.ts.map +1 -0
  82. package/daemon/dist/domain/scope/scope-approve.js +153 -0
  83. package/daemon/dist/domain/scope/scope-approve.js.map +1 -0
  84. package/daemon/dist/domain/scope/scope-audit.d.ts +9 -1
  85. package/daemon/dist/domain/scope/scope-audit.d.ts.map +1 -1
  86. package/daemon/dist/domain/scope/scope-audit.js +173 -1
  87. package/daemon/dist/domain/scope/scope-audit.js.map +1 -1
  88. package/daemon/dist/domain/topology/multi-rig-launcher.d.ts +49 -0
  89. package/daemon/dist/domain/topology/multi-rig-launcher.d.ts.map +1 -0
  90. package/daemon/dist/domain/topology/multi-rig-launcher.js +132 -0
  91. package/daemon/dist/domain/topology/multi-rig-launcher.js.map +1 -0
  92. package/daemon/dist/domain/topology/remote-up-leaf.d.ts +29 -0
  93. package/daemon/dist/domain/topology/remote-up-leaf.d.ts.map +1 -0
  94. package/daemon/dist/domain/topology/remote-up-leaf.js +60 -0
  95. package/daemon/dist/domain/topology/remote-up-leaf.js.map +1 -0
  96. package/daemon/dist/domain/topology/topology-manifest.d.ts +34 -0
  97. package/daemon/dist/domain/topology/topology-manifest.d.ts.map +1 -0
  98. package/daemon/dist/domain/topology/topology-manifest.js +195 -0
  99. package/daemon/dist/domain/topology/topology-manifest.js.map +1 -0
  100. package/daemon/dist/domain/types.d.ts +5 -0
  101. package/daemon/dist/domain/types.d.ts.map +1 -1
  102. package/daemon/dist/domain/types.js.map +1 -1
  103. package/daemon/dist/domain/up-command-router.d.ts +1 -1
  104. package/daemon/dist/domain/up-command-router.d.ts.map +1 -1
  105. package/daemon/dist/domain/up-command-router.js +34 -2
  106. package/daemon/dist/domain/up-command-router.js.map +1 -1
  107. package/daemon/dist/domain/user-settings/settings-store.d.ts +17 -0
  108. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  109. package/daemon/dist/domain/user-settings/settings-store.js +109 -0
  110. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  111. package/daemon/dist/domain/workspace/default-workspace-scaffold.d.ts.map +1 -1
  112. package/daemon/dist/domain/workspace/default-workspace-scaffold.js +51 -6
  113. package/daemon/dist/domain/workspace/default-workspace-scaffold.js.map +1 -1
  114. package/daemon/dist/domain/workspace/workspace-doctor.d.ts +11 -0
  115. package/daemon/dist/domain/workspace/workspace-doctor.d.ts.map +1 -1
  116. package/daemon/dist/domain/workspace/workspace-doctor.js +82 -0
  117. package/daemon/dist/domain/workspace/workspace-doctor.js.map +1 -1
  118. package/daemon/dist/routes/config.d.ts.map +1 -1
  119. package/daemon/dist/routes/config.js +15 -6
  120. package/daemon/dist/routes/config.js.map +1 -1
  121. package/daemon/dist/routes/files.d.ts.map +1 -1
  122. package/daemon/dist/routes/files.js +45 -1
  123. package/daemon/dist/routes/files.js.map +1 -1
  124. package/daemon/dist/routes/mission-control.d.ts.map +1 -1
  125. package/daemon/dist/routes/mission-control.js +67 -2
  126. package/daemon/dist/routes/mission-control.js.map +1 -1
  127. package/daemon/dist/routes/queue.d.ts +8 -2
  128. package/daemon/dist/routes/queue.d.ts.map +1 -1
  129. package/daemon/dist/routes/queue.js +44 -4
  130. package/daemon/dist/routes/queue.js.map +1 -1
  131. package/daemon/dist/routes/review.d.ts +3 -0
  132. package/daemon/dist/routes/review.d.ts.map +1 -0
  133. package/daemon/dist/routes/review.js +169 -0
  134. package/daemon/dist/routes/review.js.map +1 -0
  135. package/daemon/dist/routes/scope-approve.d.ts +3 -0
  136. package/daemon/dist/routes/scope-approve.d.ts.map +1 -0
  137. package/daemon/dist/routes/scope-approve.js +55 -0
  138. package/daemon/dist/routes/scope-approve.js.map +1 -0
  139. package/daemon/dist/routes/scope-audit.d.ts.map +1 -1
  140. package/daemon/dist/routes/scope-audit.js +28 -1
  141. package/daemon/dist/routes/scope-audit.js.map +1 -1
  142. package/daemon/dist/routes/slices.d.ts.map +1 -1
  143. package/daemon/dist/routes/slices.js +49 -9
  144. package/daemon/dist/routes/slices.js.map +1 -1
  145. package/daemon/dist/routes/terminal-ws.d.ts.map +1 -1
  146. package/daemon/dist/routes/terminal-ws.js +58 -21
  147. package/daemon/dist/routes/terminal-ws.js.map +1 -1
  148. package/daemon/dist/routes/up.d.ts.map +1 -1
  149. package/daemon/dist/routes/up.js +84 -0
  150. package/daemon/dist/routes/up.js.map +1 -1
  151. package/daemon/dist/routes/workspace.js +2 -2
  152. package/daemon/dist/server.d.ts +1 -0
  153. package/daemon/dist/server.d.ts.map +1 -1
  154. package/daemon/dist/server.js +14 -1
  155. package/daemon/dist/server.js.map +1 -1
  156. package/daemon/dist/startup.d.ts.map +1 -1
  157. package/daemon/dist/startup.js +18 -3
  158. package/daemon/dist/startup.js.map +1 -1
  159. package/daemon/docs/reference/agent-startup-guide.md +1 -1
  160. package/daemon/docs/reference/product-factory-vps-runbook.md +132 -0
  161. package/daemon/docs/reference/sdlc-conventions.md +187 -0
  162. package/daemon/specs/agents/conveyor/builder/agent.yaml +1 -1
  163. package/daemon/specs/agents/conveyor/builder/guidance/role.md +1 -0
  164. package/daemon/specs/agents/conveyor/lead/agent.yaml +1 -1
  165. package/daemon/specs/agents/conveyor/lead/guidance/role.md +1 -0
  166. package/daemon/specs/agents/conveyor/planner/agent.yaml +1 -1
  167. package/daemon/specs/agents/conveyor/planner/guidance/role.md +1 -0
  168. package/daemon/specs/agents/conveyor/reviewer/agent.yaml +1 -1
  169. package/daemon/specs/agents/conveyor/reviewer/guidance/role.md +1 -0
  170. package/daemon/specs/agents/design/product-designer/agent.yaml +1 -1
  171. package/daemon/specs/agents/design/product-designer/guidance/role.md +1 -0
  172. package/daemon/specs/agents/development/implementer/agent.yaml +1 -1
  173. package/daemon/specs/agents/development/implementer/guidance/role.md +1 -0
  174. package/daemon/specs/agents/development/qa/agent.yaml +1 -1
  175. package/daemon/specs/agents/development/qa/guidance/role.md +1 -0
  176. package/daemon/specs/agents/orchestration/orchestrator/agent.yaml +1 -1
  177. package/daemon/specs/agents/orchestration/orchestrator/guidance/role.md +1 -0
  178. package/daemon/specs/agents/product-management/pm/agent.yaml +1 -0
  179. package/daemon/specs/agents/product-management/pm/guidance/role.md +13 -0
  180. package/daemon/specs/agents/review/independent-reviewer/agent.yaml +1 -1
  181. package/daemon/specs/agents/review/independent-reviewer/guidance/role.md +1 -0
  182. package/daemon/specs/agents/shared/agent.yaml +2 -0
  183. package/daemon/specs/agents/shared/skills/core/mission-slice-sop/SKILL.md +115 -0
  184. package/daemon/specs/agents/shared/skills/core/openrig-operator/SKILL.md +4 -4
  185. package/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md +69 -23
  186. package/daemon/specs/agents/shared/skills/pods/orchestration-team/SKILL.md +2 -2
  187. package/daemon/specs/rigs/focused/pm-team/CULTURE.md +4 -0
  188. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/guidance/role.md +1 -1
  189. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/startup/context.md +1 -1
  190. package/daemon/specs/rigs/preview/product-team/CULTURE.md +4 -0
  191. package/dist/build-info.d.ts +8 -0
  192. package/dist/build-info.d.ts.map +1 -0
  193. package/dist/build-info.js +11 -0
  194. package/dist/build-info.js.map +1 -0
  195. package/dist/commands/config-init-workspace.d.ts.map +1 -1
  196. package/dist/commands/config-init-workspace.js +20 -4
  197. package/dist/commands/config-init-workspace.js.map +1 -1
  198. package/dist/commands/doctor.js +2 -2
  199. package/dist/commands/doctor.js.map +1 -1
  200. package/dist/commands/expand.js +1 -1
  201. package/dist/commands/expand.js.map +1 -1
  202. package/dist/commands/file.d.ts +7 -0
  203. package/dist/commands/file.d.ts.map +1 -0
  204. package/dist/commands/file.js +77 -0
  205. package/dist/commands/file.js.map +1 -0
  206. package/dist/commands/host.d.ts +34 -0
  207. package/dist/commands/host.d.ts.map +1 -0
  208. package/dist/commands/host.js +448 -0
  209. package/dist/commands/host.js.map +1 -0
  210. package/dist/commands/proof.d.ts +54 -0
  211. package/dist/commands/proof.d.ts.map +1 -0
  212. package/dist/commands/proof.js +311 -0
  213. package/dist/commands/proof.js.map +1 -0
  214. package/dist/commands/ps.d.ts +41 -7
  215. package/dist/commands/ps.d.ts.map +1 -1
  216. package/dist/commands/ps.js +343 -131
  217. package/dist/commands/ps.js.map +1 -1
  218. package/dist/commands/queue.d.ts.map +1 -1
  219. package/dist/commands/queue.js +76 -0
  220. package/dist/commands/queue.js.map +1 -1
  221. package/dist/commands/scope.d.ts.map +1 -1
  222. package/dist/commands/scope.js +140 -3
  223. package/dist/commands/scope.js.map +1 -1
  224. package/dist/commands/send.js +13 -4
  225. package/dist/commands/send.js.map +1 -1
  226. package/dist/commands/up.d.ts +2 -0
  227. package/dist/commands/up.d.ts.map +1 -1
  228. package/dist/commands/up.js +69 -2
  229. package/dist/commands/up.js.map +1 -1
  230. package/dist/commands/workspace.js +6 -6
  231. package/dist/commands/workspace.js.map +1 -1
  232. package/dist/config-store.d.ts +4 -0
  233. package/dist/config-store.d.ts.map +1 -1
  234. package/dist/config-store.js +68 -0
  235. package/dist/config-store.js.map +1 -1
  236. package/dist/cross-host-executor.d.ts +1 -0
  237. package/dist/cross-host-executor.d.ts.map +1 -1
  238. package/dist/cross-host-executor.js +4 -1
  239. package/dist/cross-host-executor.js.map +1 -1
  240. package/dist/daemon-lifecycle.d.ts.map +1 -1
  241. package/dist/daemon-lifecycle.js +4 -1
  242. package/dist/daemon-lifecycle.js.map +1 -1
  243. package/dist/host-registry.d.ts +9 -0
  244. package/dist/host-registry.d.ts.map +1 -1
  245. package/dist/host-registry.js +32 -2
  246. package/dist/host-registry.js.map +1 -1
  247. package/dist/index.d.ts.map +1 -1
  248. package/dist/index.js +7 -0
  249. package/dist/index.js.map +1 -1
  250. package/dist/lib/file-transfer.d.ts +89 -0
  251. package/dist/lib/file-transfer.d.ts.map +1 -0
  252. package/dist/lib/file-transfer.js +300 -0
  253. package/dist/lib/file-transfer.js.map +1 -0
  254. package/dist/lib/hosts/fanout-contract.d.ts +33 -0
  255. package/dist/lib/hosts/fanout-contract.d.ts.map +1 -0
  256. package/dist/lib/hosts/fanout-contract.js +23 -0
  257. package/dist/lib/hosts/fanout-contract.js.map +1 -0
  258. package/dist/lib/scope/scope-audit.d.ts +9 -1
  259. package/dist/lib/scope/scope-audit.d.ts.map +1 -1
  260. package/dist/lib/scope/scope-audit.js +173 -1
  261. package/dist/lib/scope/scope-audit.js.map +1 -1
  262. package/dist/lib/scope/templates.d.ts +1 -0
  263. package/dist/lib/scope/templates.d.ts.map +1 -1
  264. package/dist/lib/scope/templates.js +9 -0
  265. package/dist/lib/scope/templates.js.map +1 -1
  266. package/dist/lib/scope-templates/backlog-deprecation.md +16 -0
  267. package/dist/lib/scope-templates/backlog-tech-debt.md +16 -0
  268. package/dist/lib/scope-templates/bug-fix.md +16 -0
  269. package/dist/lib/scope-templates/implementation-prd.md +33 -0
  270. package/dist/lib/scope-templates/mission-placeholder.md +1 -1
  271. package/dist/lib/scope-templates/mission-progress.md +1 -1
  272. package/dist/lib/scope-templates/mission-release.md +1 -1
  273. package/dist/lib/scope-templates/placeholder.md +11 -7
  274. package/dist/lib/scope-templates/proof.md +5 -1
  275. package/dist/lib/scope-templates/release-feature.md +14 -6
  276. package/dist/lib/scope-templates/research.md +16 -0
  277. package/dist/lib/scope-templates/slice-progress.md +1 -1
  278. package/dist/version.d.ts +5 -0
  279. package/dist/version.d.ts.map +1 -1
  280. package/dist/version.js +11 -1
  281. package/dist/version.js.map +1 -1
  282. package/package.json +1 -1
  283. package/ui/dist/assets/index-D69ZhNIr.js +598 -0
  284. package/ui/dist/assets/index-DJYun-8d.css +32 -0
  285. package/ui/dist/index.html +2 -2
  286. package/ui/dist/assets/index-DYZniQcc.js +0 -597
  287. package/ui/dist/assets/index-trcb4Yf_.css +0 -32
@@ -17,6 +17,12 @@ That skill covers the broader surface, including chatroom, discovery, adopt/bind
17
17
  Your per-session startup guidance may also name additional packaged skills for your role and pod.
18
18
  Load those too. This shared overlay is only the common bootstrap floor, not the full operating manual for your seat.
19
19
 
20
+ ## Working missions and slices (the SDLC)
21
+
22
+ If your seat works missions or slices, load the packaged `mission-slice-sop` skill BEFORE authoring or building — it teaches the flow the Living Notes UI projects: intent → mini-requirements + proof contract (→ mockups for UI slices) → plan-lock (`rig scope slice approve --scope spec`) → build the locked set → QA visual compare → `rig proof add … --media` drops (the C1 drop verb — never hand-place proof files) → proof-lock (`--scope delivery`).
23
+
24
+ The conventions themselves (section names, proof-contract format, the two locks, C1 proof headers) live in ONE shipped document: `docs/reference/sdlc-conventions.md` (in the CLI package). `rig scope slice create` scaffolds the convention sections for every template kind; `rig scope audit` is the advisory backstop — it records and advises, it never blocks your work.
25
+
20
26
  ## Identity
21
27
 
22
28
  Run this first after launch or compaction to recover your identity:
@@ -1,19 +1,20 @@
1
1
  ---
2
2
  name: mission-slice-sop
3
- description: Use when working a mission or slice — how to track, prove, hand off, and carry state across agents and compaction via the canonical files (PROGRESS.md / PROOF.md / MISSION_NOTES.md / MISSION_BRIEF.md / README.md). Covers the per-file WHO/WHEN/HOW rules, the four-legs lifecycle (SCAFFOLD/POPULATE/PROJECT/VERIFY), hot-potato queue handoffs, the moment-of-truth checklist, the deterministic `rig scope audit` backstop, and the known ghost-text gotcha when reading terminal captures.
3
+ description: Use when working a mission or slice — the full SDLC flow (intent → mini-requirements + proof contract → mockups → plan-lock → build → QA visual compare → `rig proof` drops → proof-lock) plus how to track, prove, hand off, and carry state across agents and compaction via the canonical files (PROGRESS.md / PROOF.md / MISSION_NOTES.md / MISSION_BRIEF.md / README.md / IMPLEMENTATION-PRD.md). Covers the convention sections the Living Notes UI projects, the two locks, the three role contracts, per-file WHO/WHEN/HOW rules, the four-legs lifecycle, hot-potato queue handoffs, the moment-of-truth checklist, the deterministic `rig scope audit` backstop, and the ghost-text capture gotcha.
4
4
  metadata:
5
5
  openrig:
6
6
  stage: shipped
7
- last_verified: "2026-07-03"
7
+ last_verified: "2026-07-06"
8
8
  distribution_scope: product-bound
9
9
  source_evidence: |
10
- Authored + embedded in release-0.4.3 slice 32 (mission-slice-sop). The
11
- canonical SOP is the operating surface for mission/slice work: the
12
- PROGRESS / PROOF / MISSION_NOTES / MISSION_BRIEF files drive the workspace
13
- UI and carry state across agents + compaction. Operationalizes the
14
- markdown-control-plane convention (loose freeform write + deterministic
15
- verify) and the shipped `rig scope audit` classifier — the deterministic
16
- backstop is the audit, not standalone check scripts.
10
+ Authored + embedded in release-0.4.3 slice 32 (mission-slice-sop);
11
+ moved into the canonical product skill source and updated in
12
+ release-0.4.4 slice 23 (operationalize the SDLC control plane) to
13
+ teach the full SDLC flow the Living Notes UI projects: the convention
14
+ sections, the proof-contract pairing, the two staged-approval locks,
15
+ C1 proof drops, and the three role contracts. Conventions SSOT:
16
+ docs/reference/sdlc-conventions.md (shipped with the CLI package).
17
+ The deterministic backstop is the `rig scope audit` classifier.
17
18
  sibling_skills:
18
19
  - queue-handoff
19
20
  - seat-continuity-and-handover
@@ -22,17 +23,43 @@ metadata:
22
23
 
23
24
  # Mission/Slice SOP — how you work a mission & slice
24
25
 
25
- Use this skill to actually **do** mission/slice work the way OpenRig expects: track on the canonical files, prove on them, hand off through them, and survive compaction on them. **Do the work described here; do not merely explain the protocol.** The deterministic backstop is `rig scope audit` — there are no separate bundled check scripts; the audit classifier is the source of truth for adherence.
26
+ Use this skill to actually **do** mission/slice work the way OpenRig expects: author the convention sections, track on the canonical files, prove on them, hand off through them, and survive compaction on them. **Do the work described here; do not merely explain the protocol.** The deterministic backstop is `rig scope audit` — the audit classifier is the source of truth for adherence. The conventions themselves live in ONE place: **`docs/reference/sdlc-conventions.md`** (shipped with the CLI package) — this skill teaches the flow; the SSOT defines the formats.
26
27
 
27
- > The canonical files below are the **operating surface of the work** — you track on them, prove on them, hand off through them, and survive compaction on them. They are NOT side-artifacts to "maintain"; keeping them current *is* the work.
28
+ ## The SDLC flow (intent proof)
29
+
30
+ ```
31
+ intent → mini-requirements + proof contract → (UI slices: mockups)
32
+ → plan-lock (rig scope slice approve --scope spec)
33
+ → build the LOCKED set
34
+ → QA: mockup ↔ delivered VISUAL compare
35
+ → proof drops (rig proof add <slice> …)
36
+ → proof-lock (rig scope slice approve --scope delivery)
37
+ ```
38
+
39
+ 1. **Record intent** verbatim in the slice's `## Intent` section (`rig scope slice create` scaffolds it — every template kind).
40
+ 2. **Author the mini-requirements + proof contract**: `## Mini-requirements` is the concise one-glance tier (approval starts there); `## Proof contract` is a checkbox list of promised deliverables, each written as an observable outcome. UI deliverables name their planned mockup. The IMPLEMENTATION-PRD opens with the mini-requirements; everything between intent and proof is **elastic** — for a small slice the mini-requirements may BE the whole PRD.
41
+ 3. **Plan-lock**: `rig scope slice approve <slice> --scope spec` — "the PRD matches the intent; THIS artifact set is what gets built." One daemon-side write: frontmatter stamp + append-only audit row.
42
+ 4. **Build the locked set** — look at the mockups, not just the spec text.
43
+ 5. **QA visual compare**: for each deliverable, load the planned mockup, produce the real artifact in a test/demo environment, visually compare, and record the verdict.
44
+ 6. **Drop proof**: `rig proof add <slice> --artifact-type qa --verdict PASS --candidate-sha <tip> --money-evidence "…" --evidences "1,3" --media "walk.webm,panel.png" --self-check "…"` — the C1 header's closed sets validate at drop time; `--evidences` joins the drop to its proof-contract items and `--media` names the curated proof/-relative media the drop stands behind (that pairing + media set is what the UI's DELIVERED section renders).
45
+ 7. **Proof-lock**: `rig scope slice approve <slice> --scope delivery` — the terminal sign-off. Approval is freeze/sign-off, **never** proven-green: proven-green requires the recorded C1 verdicts.
46
+
47
+ ## The three role contracts
48
+
49
+ - **Planning agent:** authors intent + mini-requirements + the proof contract; produces mockups for UI deliverables and attaches them to the locked set (a UI slice with no mockup is an incomplete plan; non-UI slices have none — not a gate); locks the plan.
50
+ - **Build agent:** builds against the LOCKED set only; looks at the mockups.
51
+ - **QA agent (owns the compare):** visually compares planned vs delivered per deliverable, records verdict + note via proof drops, and **curates** the canonical proof set (bounded, mapped to deliverables; the fix-loop pile stays in `proof/`, one drill-in down). On mismatch: fix-and-re-review or kick back with the reason — never escalate a raw mismatch to the human.
28
52
 
29
53
  ## The canonical files (the operating surface)
30
54
 
31
- - **README.md** (mission + slice) the overview: what the mission/slice is + why. The **mission README carries this SOP at its bottom.**
55
+ > The canonical files below are the **operating surface of the work** you track on them, prove on them, hand off through them, and survive compaction on them. They are NOT side-artifacts to "maintain"; keeping them current *is* the work.
56
+
57
+ - **README.md** (mission + slice) — the overview, OPENING with the convention sections (`## Intent` / `## Mini-requirements` / `## Proof contract`). The **mission README carries this SOP at its bottom.**
58
+ - **IMPLEMENTATION-PRD.md** — the full PRD; opens with the mini-requirements; the `## Proof contract` here is what the UI's DELIVERED pairing joins proof against.
32
59
  - **PROGRESS.md** — the live delivery state. One line per outcome; links down for detail.
33
60
  - **MISSION_NOTES.md** — the accruing handoff + tribal-knowledge doc. Blank-slate onboarding + **compaction-restore** read it. `§1` top-of-mind + per-seat `§A–§X`.
34
61
  - **MISSION_BRIEF.md** — the steering doc (the UI "Steering" tab). The 7-section schema.
35
- - **PROOF.md** (+ `proof/`) — acceptance evidence. **A slice is not done until every plan success-criterion has proof.**
62
+ - **PROOF.md** (+ `proof/`) — acceptance evidence. **A slice is not done until every proof-contract item has evidence.**
36
63
 
37
64
  ## Per-file rules — WHO / WHEN / HOW
38
65
 
@@ -41,10 +68,10 @@ Use this skill to actually **do** mission/slice work the way OpenRig expects: tr
41
68
  - **WHEN:** after every slice-done **AND every commit**; on any material state change.
42
69
  - **HOW:** one line per outcome (checkbox), link down for detail (workstream-continuity format); keep frontmatter `stage`/`verified` honest.
43
70
 
44
- ### PROOF.md
71
+ ### PROOF.md + proof/
45
72
  - **WHO:** the impl/QA pair that worked the slice.
46
- - **WHEN:** a slice is **NOT "done"** until PROOF.md exists and every plan success-criterion has evidence.
47
- - **HOW:** proof maps **1:1 to the plan's acceptance/success criteria** — each criterion gets an evidence artifact (screenshot/capture/log) in `proof/` + a line in PROOF.md stating what it proves. No proof → not done.
73
+ - **WHEN:** a slice is **NOT "done"** until every proof-contract item has evidence.
74
+ - **HOW:** proof maps **1:1 to the proof contract's deliverables** — put media under `proof/`, then ATTACH it with `rig proof add <slice> … --evidences <item> --media <files>` (the drop writes the C1 header the DELIVERED pairing joins on) + a line in PROOF.md stating what it proves. **Hand-placing files in `proof/` without a drop is the anti-pattern** — the deliverable stays unpaired and `unverified`. No proof → not done.
48
75
 
49
76
  ### MISSION_NOTES.md
50
77
  - **WHO:** any agent updates `§1` (top-of-mind); each seat owns + appends to its own `§A–§X` section.
@@ -59,11 +86,11 @@ Use this skill to actually **do** mission/slice work the way OpenRig expects: tr
59
86
  ### README.md
60
87
  - **WHO:** the author at creation; refreshed on rescope.
61
88
  - **WHEN:** at mission/slice creation + when scope/theme changes.
62
- - **HOW:** overview + honest frontmatter (`id`/`stage`/`verified`); the mission README carries this SOP at its bottom.
89
+ - **HOW:** overview + honest frontmatter (`id`/`stage`/`verified`) + the convention sections up top; the mission README carries this SOP at its bottom.
63
90
 
64
91
  ## The lifecycle (4 legs)
65
92
 
66
- **SCAFFOLD** (`rig scope` creates the files from templates) → **POPULATE** (agents fill them as work happens, per the rules above) → **PROJECT** (the workspace UI reads them into tabs) → **VERIFY** (`rig scope audit` + the validation scripts check adherence). "Loose freeform write + deterministic verify."
93
+ **SCAFFOLD** (`rig scope` creates the files from templates — every slice kind emits the convention sections + `proof/` + the IMPLEMENTATION-PRD skeleton) → **POPULATE** (agents fill them as work happens, per the rules above) → **PROJECT** (the Living Notes UI reads them into INTENT → PLAN → DELIVERED) → **VERIFY** (`rig scope audit` checks adherence). "Loose freeform write + deterministic verify."
67
94
 
68
95
  ## Hot-potato (handoffs)
69
96
 
@@ -71,7 +98,7 @@ End every turn by passing the ball — a `rig queue` handoff to the next agent (
71
98
 
72
99
  ## Verify (deterministic backstop)
73
100
 
74
- Run `rig scope audit` at slice-close. The validation scripts flag: committed-without-touching-PROGRESS; slice-marked-done-without-PROOF; active-mission-without-MISSION_NOTES; MISSION_BRIEF off-schema. **Fix the flag, don't suppress it.**
101
+ Run `rig scope audit` at slice-close. It flags: committed-without-touching-PROGRESS; slice-marked-done-without-PROOF; active-mission-without-MISSION_NOTES; MISSION_BRIEF off-schema; proof artifacts violating the C1 header; missing IMPLEMENTATION-PRD on a building slice; missing convention sections (`## Intent`, a well-formed `## Proof contract`, the UI-slice mockup ref). Every convention check is **advisory / fail-open** — it records and advises, never blocks a write. **Fix the flag, don't suppress it.**
75
102
 
76
103
  ## Reading terminal captures — KNOWN GOTCHA: ghost-text autocomplete is NOT real
77
104
 
@@ -81,7 +108,8 @@ When you `rig capture` a pane, **greyed / ghost autocomplete suggestions are NOT
81
108
 
82
109
  ## Moment-of-truth checklist
83
110
 
84
- - **Finishing a slice?** → PROOF complete (every criterion has evidence)? PROGRESS updated? MISSION_NOTES `§1` refreshed? Handed off via queue?
111
+ - **Starting a slice?** → intent recorded verbatim? mini-requirements + proof contract authored? mockups attached (UI slices)? plan locked (`--scope spec`)?
112
+ - **Finishing a slice?** → every proof-contract item has curated evidence via `rig proof add … --evidences --media` (C1 drops — never only hand-placed files)? PROGRESS updated? MISSION_NOTES `§1` refreshed? proof locked (`--scope delivery`)? Handed off via queue?
85
113
  - **Committing?** → PROGRESS updated?
86
114
  - **Compacting?** → filed your state in MISSION_NOTES?
87
- - **Starting on a mission?** → read the mission README (incl. this SOP) + MISSION_NOTES?
115
+ - **Starting on a mission?** → read the mission README (incl. this SOP) + MISSION_NOTES + `docs/reference/sdlc-conventions.md`?
@@ -61,7 +61,7 @@ Default posture on this host now:
61
61
 
62
62
  Most work in OpenRig reduces to this loop:
63
63
  - recover identity: `rig whoami --json`
64
- - inspect inventory: `rig ps --nodes --json`
64
+ - inspect inventory: `rig ps` (fleet map) / `rig ps --nodes -A --json` (fleet nodes)
65
65
  - read context: `rig transcript ...`, `rig ask ...`, `rig chatroom history ...`
66
66
  - act: `rig send`, `rig capture`, `rig broadcast`, lifecycle commands
67
67
 
@@ -90,13 +90,13 @@ If the daemon is unreachable but identity can still be inferred, `--json` may re
90
90
  ## Inventory and Monitoring
91
91
 
92
92
  ```bash
93
- rig ps
94
- rig ps --json
95
- rig ps --nodes
96
- rig ps --nodes --json
93
+ rig ps # ALL active rigs, one compact row each (v0.4.4 default)
94
+ rig ps --json # bare array, all non-archived rigs
95
+ rig ps --nodes # current rig's nodes (session default, local only)
96
+ rig ps --nodes -A --json # fleet node inventory (explicit)
97
97
  ```
98
98
 
99
- Use `rig ps --nodes --json` for the current node inventory across rigs. It is the best machine-readable operator surface for:
99
+ Use `rig ps --nodes -A --json` for the current node inventory across rigs (v0.4.4: `--nodes` needs an explicit scope outside a managed session). It is the best machine-readable operator surface for:
100
100
  - session name
101
101
  - runtime
102
102
  - session/startup status
@@ -386,7 +386,7 @@ rig whoami --json
386
386
 
387
387
  Notes:
388
388
  - for tmux-backed self-attach, `rig whoami --json` is the right verification
389
- - for raw/external self-attach, `rig ps --nodes --json` is currently the more reliable verification surface
389
+ - for raw/external self-attach, `rig ps --nodes -A --json` is currently the more reliable verification surface
390
390
  - if the current shell is outside tmux, pass `--display-name <name>` when you want a stable human session label recorded
391
391
 
392
392
  ### Adopt a topology and bind live sessions
@@ -453,13 +453,13 @@ Verification loop:
453
453
  ```bash
454
454
  rig discover --json
455
455
  rig adopt <fragment.yaml> --bindings-file <bindings.yaml> --target-rig <rigId>
456
- rig ps --nodes --json
456
+ rig ps --nodes -A --json
457
457
  rig export <rigId> -o rig.yaml
458
458
  ```
459
459
 
460
460
  Success looks like:
461
461
  - the new sessions stop appearing in `rig discover`
462
- - the new logical IDs appear in `rig ps --nodes --json`
462
+ - the new logical IDs appear in `rig ps --nodes -A --json`
463
463
  - `rig export` includes the new pod
464
464
 
465
465
  ### Mixed-origin rigs are allowed
@@ -477,7 +477,7 @@ Current safety rule:
477
477
  The proven operator pattern is:
478
478
  - keep one OpenRig manager session outside the rig it manages
479
479
  - address the target by rig name, not cached rig ID
480
- - resolve the current owner from fresh `rig ps --nodes --json`
480
+ - resolve the current owner from fresh `rig ps --nodes -A --json`
481
481
  - send the manager the spec path, bindings path, and verification steps with `rig send`
482
482
 
483
483
  This lets ordinary agents ask the manager for OpenRig help instead of every agent needing to be an OpenRig expert.
@@ -548,7 +548,7 @@ When the CLI behaves strangely, use the smallest truthful check first:
548
548
  ```bash
549
549
  rig whoami --json
550
550
  rig daemon status
551
- rig ps --nodes --json
551
+ rig ps --nodes -A --json
552
552
  ```
553
553
 
554
554
  Specific operator rules:
@@ -584,7 +584,7 @@ Design assumptions that hold in the shipped CLI:
584
584
 
585
585
  1. `rig whoami --json`
586
586
  2. `rig transcript <your-session> --tail 100`
587
- 3. `rig ps --nodes --json`
587
+ 3. `rig ps --nodes -A --json`
588
588
  4. `rig chatroom history <rig> --limit 50`
589
589
 
590
590
  ## Commands That Do Not Exist
@@ -0,0 +1,12 @@
1
+ export interface BuildInfo {
2
+ semver: string | null;
3
+ commit: string | null;
4
+ dirty: boolean | null;
5
+ builtAt: string | null;
6
+ }
7
+ export declare const BUILD_INFO: BuildInfo;
8
+ /** The additive /healthz fields: the four stamp keys when stamped, an EMPTY
9
+ * object when dev — existing consumers (including exact-body probes against
10
+ * dev daemons) see no change. */
11
+ export declare function stampFields(info?: BuildInfo): Record<string, unknown>;
12
+ //# sourceMappingURL=build-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-info.d.ts","sourceRoot":"","sources":["../src/build-info.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,UAAU,EAAE,SAKxB,CAAC;AAEF;;kCAEkC;AAClC,wBAAgB,WAAW,CAAC,IAAI,GAAE,SAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGjF"}
@@ -0,0 +1,11 @@
1
+ // GENERATED by scripts/build-package.sh — the packaged build-identity stamp.
2
+ export const BUILD_INFO = {
3
+ semver: "0.4.4",
4
+ commit: "ac75777a3cad318592dae9d616a09d824f1398e5",
5
+ dirty: false,
6
+ builtAt: "2026-07-06T08:21:05Z",
7
+ };
8
+ export function stampFields(info = BUILD_INFO) {
9
+ if (!info.commit) return {};
10
+ return { semver: info.semver, commit: info.commit, dirty: info.dirty, builtAt: info.builtAt };
11
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-info.js","sourceRoot":"","sources":["../src/build-info.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,+DAA+D;AAC/D,wEAAwE;AACxE,iEAAiE;AASjE,MAAM,CAAC,MAAM,UAAU,GAAc;IACnC,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;CACd,CAAC;AAEF;;kCAEkC;AAClC,MAAM,UAAU,WAAW,CAAC,OAAkB,UAAU;IACtD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AAChG,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { Migration } from "../migrate.js";
2
+ /**
3
+ * OPR.0.4.4.19 FR-5 — queue_items.evidence_ref column.
4
+ *
5
+ * Adds `evidence_ref TEXT` (NULLABLE) to queue_items: a pointer to the
6
+ * durable artifact a human judges when the item is human-routed (convention
7
+ * C3), authored via `--evidence-ref` on `rig queue create` / `handoff` and
8
+ * persistable at park time (`rig queue block`, FR-6). Follows the 044
9
+ * summary-column pattern exactly: additive, nullable, defensive
10
+ * detectQueueColumn read so pre-048 fixtures degrade.
11
+ *
12
+ * NULLABLE by contract: required ONLY for human-routed items (the §5
13
+ * predicate, enforced at the domain write path — FR-4/FR-5), so ordinary
14
+ * agent-to-agent rows stay NULL forever (BR-1 zero-friction). Do NOT make
15
+ * this NOT NULL or add a default.
16
+ */
17
+ export declare const queueItemEvidenceRefSchema: Migration;
18
+ //# sourceMappingURL=048_queue_item_evidence_ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"048_queue_item_evidence_ref.d.ts","sourceRoot":"","sources":["../../../src/db/migrations/048_queue_item_evidence_ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAKxC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OPR.0.4.4.19 FR-5 — queue_items.evidence_ref column.
3
+ *
4
+ * Adds `evidence_ref TEXT` (NULLABLE) to queue_items: a pointer to the
5
+ * durable artifact a human judges when the item is human-routed (convention
6
+ * C3), authored via `--evidence-ref` on `rig queue create` / `handoff` and
7
+ * persistable at park time (`rig queue block`, FR-6). Follows the 044
8
+ * summary-column pattern exactly: additive, nullable, defensive
9
+ * detectQueueColumn read so pre-048 fixtures degrade.
10
+ *
11
+ * NULLABLE by contract: required ONLY for human-routed items (the §5
12
+ * predicate, enforced at the domain write path — FR-4/FR-5), so ordinary
13
+ * agent-to-agent rows stay NULL forever (BR-1 zero-friction). Do NOT make
14
+ * this NOT NULL or add a default.
15
+ */
16
+ export const queueItemEvidenceRefSchema = {
17
+ name: "048_queue_item_evidence_ref.sql",
18
+ sql: `
19
+ ALTER TABLE queue_items ADD COLUMN evidence_ref TEXT;
20
+ `,
21
+ };
22
+ //# sourceMappingURL=048_queue_item_evidence_ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"048_queue_item_evidence_ref.js","sourceRoot":"","sources":["../../../src/db/migrations/048_queue_item_evidence_ref.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAc;IACnD,IAAI,EAAE,iCAAiC;IACvC,GAAG,EAAE;;GAEJ;CACF,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { AggregatedPayload } from "../hosts/fanout-contract.js";
2
+ import type { HostRegistryLoadResult } from "../hosts/hosts-registry-reader.js";
3
+ /** The aggregation READ deadline class (per-host bound). Distinct from the
4
+ * up-leaf's long-running budget by design — named here, passed explicitly. */
5
+ export declare const ATTENTION_READ_TIMEOUT_MS = 5000;
6
+ /** Fixed fan-out cap — a poll across a handful of hosts; adaptive
7
+ * throttling is out of scope (same v1 posture as the topology walker). */
8
+ export declare const ATTENTION_FANOUT_CONCURRENCY = 4;
9
+ export type AttentionItem = Record<string, unknown>;
10
+ export interface AttentionAggregatorDeps {
11
+ /** The SAME query the shipped /api/queue/list?attention=1 route runs. */
12
+ listLocalAttention: () => AttentionItem[];
13
+ /** settings-store.listFeedHostSubscriptions (the G15-P1 dynamic class). */
14
+ listSubscriptions: () => Array<{
15
+ hostId: string;
16
+ enabled: boolean;
17
+ }>;
18
+ /** S11's shared reader — called lazily, only when a remote sub exists. */
19
+ loadRegistry: () => HostRegistryLoadResult;
20
+ fetchImpl?: typeof fetch;
21
+ env?: Record<string, string | undefined>;
22
+ readFile?: (path: string) => string;
23
+ /** Test override; production uses ATTENTION_READ_TIMEOUT_MS. */
24
+ timeoutMs?: number;
25
+ concurrency?: number;
26
+ }
27
+ /** One merged attention payload: local ALWAYS included (stamped with the
28
+ * contract's LOCAL_HOST_ID), each enabled remote host fanned out under the
29
+ * read deadline + fixed cap, per-host status complete by construction. */
30
+ export declare function aggregateAttention(deps: AttentionAggregatorDeps): Promise<AggregatedPayload<AttentionItem>>;
31
+ //# sourceMappingURL=attention-aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-aggregator.d.ts","sourceRoot":"","sources":["../../../src/domain/feed/attention-aggregator.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,6BAA6B,CAAC;AAEpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAIhF;+EAC+E;AAC/E,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;2EAC2E;AAC3E,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,aAAa,EAAE,CAAC;IAC1C,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrE,0EAA0E;IAC1E,YAAY,EAAE,MAAM,sBAAsB,CAAC;IAC3C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgED;;2EAE2E;AAC3E,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAgCjH"}
@@ -0,0 +1,119 @@
1
+ // OPR.0.4.4.15 FR-1 — daemon-side attention aggregation (the SEE architecture,
2
+ // call A): the LOCAL daemon fans out server-side to each subscribed host's
3
+ // shipped GET /api/queue/list?attention=1 over the registered HTTP+bearer
4
+ // transport, merges, and returns ONE AggregatedPayload. Bearer tokens never
5
+ // reach the browser — this module is the only place remote hosts are
6
+ // contacted for the feed.
7
+ //
8
+ // Reuse, not reimplementation: the LOCAL leg invokes the SAME
9
+ // QueueRepository.listAttention query the existing route uses (injected at
10
+ // the route wiring); the registry is read through S11's shared
11
+ // hosts-registry-reader (interface cell — never re-parsed); the remote hop
12
+ // rides the shared remote-daemon-http core with the READ deadline class
13
+ // named EXPLICITLY at this call-site (arch sharpening: 5s — a poll, not a
14
+ // bootstrap; the up-leaf's 120s budget lives at ITS call-site).
15
+ //
16
+ // FR-1/R15-2 honesty: EVERY subscribed host appears in hosts[] on EVERY
17
+ // call — ok, unreachable, auth-failed, or unsupported-transport — never
18
+ // all-or-nothing, never a silently thinner feed. The registry is read ONLY
19
+ // when ≥1 remote subscription is enabled (zero-config never touches it).
20
+ import { LOCAL_HOST_ID } from "../hosts/fanout-contract.js";
21
+ import { resolveHost } from "../hosts/hosts-registry-reader.js";
22
+ import { remoteJsonRequest } from "../hosts/remote-daemon-http.js";
23
+ /** The aggregation READ deadline class (per-host bound). Distinct from the
24
+ * up-leaf's long-running budget by design — named here, passed explicitly. */
25
+ export const ATTENTION_READ_TIMEOUT_MS = 5_000;
26
+ /** Fixed fan-out cap — a poll across a handful of hosts; adaptive
27
+ * throttling is out of scope (same v1 posture as the topology walker). */
28
+ export const ATTENTION_FANOUT_CONCURRENCY = 4;
29
+ async function readHostAttention(hostId, reg, deps) {
30
+ if (!reg.ok) {
31
+ // Registry unreadable: every subscribed host reports it honestly —
32
+ // the operator sees ONE actionable error per host row, local items
33
+ // are untouched.
34
+ return { status: { hostId, status: "unreachable", error: reg.error }, items: [] };
35
+ }
36
+ const resolved = resolveHost(reg.registry, hostId);
37
+ if (!resolved.ok) {
38
+ return { status: { hostId, status: "unreachable", error: resolved.error }, items: [] };
39
+ }
40
+ if (resolved.host.transport !== "http") {
41
+ // R15-2 (the whoami --all-hosts precedent): SSH-declared hosts are a
42
+ // structured per-host status, never a silently thinner feed.
43
+ return {
44
+ status: { hostId, status: "unsupported-transport", error: `host '${hostId}' is SSH-declared; the aggregated feed read requires an http-transport registry entry (url + bearer)` },
45
+ items: [],
46
+ };
47
+ }
48
+ const res = await remoteJsonRequest(resolved.host, "/api/queue/list?attention=1", {
49
+ method: "GET",
50
+ timeoutMs: deps.timeoutMs ?? ATTENTION_READ_TIMEOUT_MS,
51
+ fetchImpl: deps.fetchImpl,
52
+ env: deps.env,
53
+ readFile: deps.readFile,
54
+ });
55
+ if (res.ok) {
56
+ const arr = Array.isArray(res.payload) ? res.payload : [];
57
+ return { status: { hostId, status: "ok" }, items: arr.map((i) => ({ ...i, hostId })) };
58
+ }
59
+ switch (res.kind) {
60
+ case "bearer":
61
+ return { status: { hostId, status: "auth-failed", error: res.detail, failedStep: "permission-gate" }, items: [] };
62
+ case "http":
63
+ if (res.status === 401 || res.status === 403) {
64
+ return { status: { hostId, status: "auth-failed", error: `HTTP ${res.status}${res.detail ? `: ${res.detail}` : ""}`, failedStep: "permission-gate" }, items: [] };
65
+ }
66
+ // A remote 4xx/5xx on the read: the feed cannot show that host's
67
+ // items — the closest closed-enum truth is unreachable, with the
68
+ // remote's own status/text as the honest detail and the shipped
69
+ // FailedStep vocabulary riding as the additive detail field.
70
+ return { status: { hostId, status: "unreachable", error: `HTTP ${res.status}${res.detail ? `: ${res.detail}` : ""}`, failedStep: "remote-command-failed" }, items: [] };
71
+ case "timeout":
72
+ return {
73
+ status: {
74
+ hostId,
75
+ status: "unreachable",
76
+ error: res.phase === "body" ? `read timed out: response headers arrived (HTTP ${res.status}) but the body never completed` : `read timed out after ${deps.timeoutMs ?? ATTENTION_READ_TIMEOUT_MS}ms`,
77
+ failedStep: "remote-daemon-unreachable",
78
+ },
79
+ items: [],
80
+ };
81
+ case "network":
82
+ return { status: { hostId, status: "unreachable", error: res.detail, failedStep: "remote-daemon-unreachable" }, items: [] };
83
+ }
84
+ }
85
+ /** One merged attention payload: local ALWAYS included (stamped with the
86
+ * contract's LOCAL_HOST_ID), each enabled remote host fanned out under the
87
+ * read deadline + fixed cap, per-host status complete by construction. */
88
+ export async function aggregateAttention(deps) {
89
+ const localItems = deps.listLocalAttention().map((i) => ({ ...i, hostId: LOCAL_HOST_ID }));
90
+ const hosts = [{ hostId: LOCAL_HOST_ID, status: "ok" }];
91
+ const items = [...localItems];
92
+ const subs = deps.listSubscriptions().filter((s) => s.enabled);
93
+ if (subs.length === 0) {
94
+ // Zero remote subscriptions: the registry is NEVER read; the payload
95
+ // is today's local feed plus the local status row.
96
+ return { items, hosts };
97
+ }
98
+ const reg = deps.loadRegistry();
99
+ const outcomes = new Array(subs.length);
100
+ let next = 0;
101
+ const cap = Math.max(1, Math.min(deps.concurrency ?? ATTENTION_FANOUT_CONCURRENCY, subs.length));
102
+ const workers = Array.from({ length: cap }, async () => {
103
+ while (true) {
104
+ const i = next;
105
+ if (i >= subs.length)
106
+ return;
107
+ next += 1;
108
+ outcomes[i] = await readHostAttention(subs[i].hostId, reg, deps);
109
+ }
110
+ });
111
+ await Promise.all(workers);
112
+ // Subscription order preserved in both arrays (deterministic payload).
113
+ for (const outcome of outcomes) {
114
+ hosts.push(outcome.status);
115
+ items.push(...outcome.items);
116
+ }
117
+ return { items, hosts };
118
+ }
119
+ //# sourceMappingURL=attention-aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-aggregator.js","sourceRoot":"","sources":["../../../src/domain/feed/attention-aggregator.ts"],"names":[],"mappings":"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"}
@@ -28,9 +28,9 @@ export declare class WriteConflictError extends Error {
28
28
  constructor(currentMtime: string, currentContentHash: string, details: Record<string, unknown>);
29
29
  }
30
30
  export declare class FileWriteError extends Error {
31
- readonly code: "stat_failed" | "tmp_write_failed" | "rename_failed" | "audit_write_failed";
31
+ readonly code: "stat_failed" | "tmp_write_failed" | "rename_failed" | "audit_write_failed" | "target_exists";
32
32
  readonly details?: Record<string, unknown> | undefined;
33
- constructor(code: "stat_failed" | "tmp_write_failed" | "rename_failed" | "audit_write_failed", message: string, details?: Record<string, unknown> | undefined);
33
+ constructor(code: "stat_failed" | "tmp_write_failed" | "rename_failed" | "audit_write_failed" | "target_exists", message: string, details?: Record<string, unknown> | undefined);
34
34
  }
35
35
  export interface FileWriteServiceOpts {
36
36
  /** Allowlist roots resolved from env at startup (re-resolved per request is fine; cheap). */
@@ -53,6 +53,15 @@ export declare class FileWriteService {
53
53
  * success.
54
54
  */
55
55
  writeAtomic(req: FileWriteRequest): FileWriteResult;
56
+ /**
57
+ * Atomically CREATE a new allowlisted file (OPR.0.4.4.20 FR-6 — the frozen
58
+ * review export). Exclusive: throws `target_exists` when the file is
59
+ * already there (frozen exports are point-in-time snapshots, never
60
+ * rewritten; re-invoking the freeze for the same approval is an idempotent
61
+ * no-op at the caller). Same temp-write + fsync + audit-row machinery as
62
+ * writeAtomic — one writer family, no parallel write path.
63
+ */
64
+ createAtomic(req: Omit<FileWriteRequest, "expectedMtime" | "expectedContentHash">): FileWriteResult;
56
65
  private appendAuditRow;
57
66
  /** Test/debug helper: returns the configured audit file path. */
58
67
  getAuditFilePath(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"file-write-service.d.ts","sourceRoot":"","sources":["../../../src/domain/files/file-write-service.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,YAAY,EAAE,MAAM;aACpB,kBAAkB,EAAE,MAAM;aAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFhC,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKnD;AAED,qBAAa,cAAe,SAAQ,KAAK;aAErB,IAAI,EAAE,aAAa,GAAG,kBAAkB,GAAG,eAAe,GAAG,oBAAoB;aAEjF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,IAAI,EAAE,aAAa,GAAG,kBAAkB,GAAG,eAAe,GAAG,oBAAoB,EACjG,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAKpD;AAED,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;gBAErB,IAAI,EAAE,oBAAoB;IAMtC;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,gBAAgB,GAAG,eAAe;IAuGnD,OAAO,CAAC,cAAc;IAKtB,iEAAiE;IACjE,gBAAgB,IAAI,MAAM;CAG3B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE1D"}
1
+ {"version":3,"file":"file-write-service.d.ts","sourceRoot":"","sources":["../../../src/domain/files/file-write-service.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,YAAY,EAAE,MAAM;aACpB,kBAAkB,EAAE,MAAM;aAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFhC,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKnD;AAED,qBAAa,cAAe,SAAQ,KAAK;aAErB,IAAI,EAAE,aAAa,GAAG,kBAAkB,GAAG,eAAe,GAAG,oBAAoB,GAAG,eAAe;aAEnG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,IAAI,EAAE,aAAa,GAAG,kBAAkB,GAAG,eAAe,GAAG,oBAAoB,GAAG,eAAe,EACnH,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAKpD;AAED,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;gBAErB,IAAI,EAAE,oBAAoB;IAMtC;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,gBAAgB,GAAG,eAAe;IAuGnD;;;;;;;OAOG;IACH,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,GAAG,qBAAqB,CAAC,GAAG,eAAe;IAqEnG,OAAO,CAAC,cAAc;IAKtB,iEAAiE;IACjE,gBAAgB,IAAI,MAAM;CAG3B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE1D"}
@@ -165,6 +165,83 @@ export class FileWriteService {
165
165
  byteCountDelta,
166
166
  };
167
167
  }
168
+ /**
169
+ * Atomically CREATE a new allowlisted file (OPR.0.4.4.20 FR-6 — the frozen
170
+ * review export). Exclusive: throws `target_exists` when the file is
171
+ * already there (frozen exports are point-in-time snapshots, never
172
+ * rewritten; re-invoking the freeze for the same approval is an idempotent
173
+ * no-op at the caller). Same temp-write + fsync + audit-row machinery as
174
+ * writeAtomic — one writer family, no parallel write path.
175
+ */
176
+ createAtomic(req) {
177
+ const target = resolveAllowedPath(this.allowlist, req.rootName, req.path);
178
+ if (fs.existsSync(target)) {
179
+ throw new FileWriteError("target_exists", `refusing to overwrite existing file '${target}'`, { target });
180
+ }
181
+ const tmpName = `.openrig-write-${process.pid}-${Math.random().toString(36).slice(2, 10)}-${path.basename(target)}`;
182
+ const tmpPath = path.join(path.dirname(target), tmpName);
183
+ let tmpFd = null;
184
+ try {
185
+ tmpFd = fs.openSync(tmpPath, "w");
186
+ fs.writeFileSync(tmpFd, req.content);
187
+ fs.fsyncSync(tmpFd);
188
+ }
189
+ catch (err) {
190
+ try {
191
+ if (tmpFd !== null)
192
+ fs.closeSync(tmpFd);
193
+ }
194
+ catch { /* ignore */ }
195
+ try {
196
+ fs.unlinkSync(tmpPath);
197
+ }
198
+ catch { /* ignore */ }
199
+ throw new FileWriteError("tmp_write_failed", `failed to write+fsync temp file at '${tmpPath}': ${err instanceof Error ? err.message : String(err)}`, { target, tmpPath });
200
+ }
201
+ finally {
202
+ try {
203
+ if (tmpFd !== null)
204
+ fs.closeSync(tmpFd);
205
+ }
206
+ catch { /* ignore */ }
207
+ }
208
+ try {
209
+ // linkSync fails if the target appeared meanwhile — true exclusive create.
210
+ fs.linkSync(tmpPath, target);
211
+ fs.unlinkSync(tmpPath);
212
+ }
213
+ catch (err) {
214
+ try {
215
+ fs.unlinkSync(tmpPath);
216
+ }
217
+ catch { /* ignore */ }
218
+ const code = err.code === "EEXIST" ? "target_exists" : "rename_failed";
219
+ throw new FileWriteError(code, `failed to atomically create '${target}': ${err instanceof Error ? err.message : String(err)}`, { target, tmpPath });
220
+ }
221
+ const newStat = fs.statSync(target);
222
+ const newContent = fs.readFileSync(target);
223
+ const newMtime = newStat.mtime.toISOString();
224
+ const newContentHash = sha256Hex(newContent);
225
+ const auditRow = {
226
+ ts: this.now().toISOString(),
227
+ actor: req.actor,
228
+ root: req.rootName,
229
+ path: req.path,
230
+ absolutePath: target,
231
+ prevMtime: null,
232
+ newMtime,
233
+ prevContentHash: null,
234
+ newContentHash,
235
+ byteCountDelta: newContent.byteLength,
236
+ };
237
+ try {
238
+ this.appendAuditRow(auditRow);
239
+ }
240
+ catch (err) {
241
+ throw new FileWriteError("audit_write_failed", `create succeeded but audit append failed: ${err instanceof Error ? err.message : String(err)}`, { target, auditFilePath: this.auditFilePath, ...auditRow });
242
+ }
243
+ return { absolutePath: target, newMtime, newContentHash, byteCountDelta: newContent.byteLength };
244
+ }
168
245
  appendAuditRow(row) {
169
246
  fs.mkdirSync(path.dirname(this.auditFilePath), { recursive: true });
170
247
  fs.appendFileSync(this.auditFilePath, `${JSON.stringify(row)}\n`);